using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Linq.Expressions;

namespace Krile.Cores.PManElems
{
    /// <summary>
    /// Plugin Authorization Helper
    /// </summary>
    internal sealed class PluginAuth
    {
        private Dictionary<string, PluginAuthData> authDatas;

        /// <summary>
        /// Plugin data constructor
        /// </summary>
        internal PluginAuth()
        {
            authDatas = new Dictionary<string, PluginAuthData>();
        }

        public string[] SerializeEncryptedKeys
        {
            get
            {
                List<string> returns = new List<string>();
                foreach (var data in authDatas.Values)
                {
                    returns.Add(Subsystem.Security.EncryptString(data.ReflectString));
                }
                return returns.ToArray();
            }

            set
            {
                if (value == null) return;
                foreach (var val in value)
                {
                    try
                    {
                        string raw = Subsystem.Security.DecryptString(val);
                        var pad = new PluginAuthData(raw);
                        if (authDatas.ContainsKey(pad.ClassName))
                            Subsystem.Debugger.AddReport(new KrilePluginExecutionException("Classname duplicated in reflection."));
                        else
                            authDatas.Add(pad.ClassName, pad);
                    }
                    catch(Exception e)
                    {
                        Subsystem.Debugger.AddReport(e);
                    }
                }
            }
        }

        /// <summary>
        /// Confirm plugin authorization to user
        /// </summary>
        /// <param name="pd"></param>
        /// <returns></returns>
        internal bool ConfirmAuthorize(PluginData pd)
        {
            if (authDatas.ContainsKey(pd.ClassName))
            {
                //Already existed plugin
                if (authDatas[pd.ClassName].Authorized)
                    return true;
                else
                    return false;
            }
            else
            {
                //New plugin
                if (SetAuthorize(pd, true))
                    return true;
                else
                    return false;
            }
        }

        /// <summary>
        /// Get plugin authorization to user
        /// </summary>
        /// <param name="pd"></param>
        /// <returns></returns>
        internal bool GetAuthorize(PluginData pd)
        {
            if (authDatas.ContainsKey(pd.ClassName))
            {
                //Already existed plugin
                if (authDatas[pd.ClassName].Authorized)
                    return true;
                else
                    return false;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Set plugin's authorize state with UI
        /// </summary>
        /// <param name="pd">target</param>
        /// <param name="newAuth">new authorize state value</param>
        /// <returns>succeeded</returns>
        internal bool SetAuthorize(PluginData pd, bool newAuthState)
        {
            if (pd.Loadable == newAuthState) return true;
            if (newAuthState)
            {
                using (var auth = new Forms.Dialogs.Show.Authorize())
                {
                    auth.PluginName = pd.Name;
                    auth.PluginPath = pd.FilePath;
                    auth.PluginClass = pd.ClassName;
                    auth.PluginDescription = pd.Description;
                    var result = auth.ShowDialog();
                    switch (result)
                    {
                        case DialogResult.Yes:
                            //Accept
                            setAuthValue(pd, true);
                            return true;
                        case DialogResult.No:
                            //Refuse
                            setAuthValue(pd, false);
                            return false;
                        default:
                            //Skip or Unknown
                            return false;
                    }
                }
            }
            else
            {
                authDatas[pd.ClassName].Authorized = false;
                return true;
            }
        }

        /// <summary>
        /// Set auth value(if dict uncontains key,I'll make it.)
        /// </summary>
        /// <param name="pd">target</param>
        /// <param name="value">setvalue</param>
        private void setAuthValue(PluginData pd, bool value)
        {
            if (!authDatas.ContainsKey(pd.ClassName))
            {
                authDatas.Add(pd.ClassName, new PluginAuthData(pd));
            }
            authDatas[pd.ClassName].Authorized = value;
        }

        /// <summary>
        /// Authorize data garbage collection
        /// </summary>
        /// <remarks>
        /// It deletes authorize datas which not existed in Krile now.
        /// </remarks>
        internal void CollectGarbage()
        {
            PluginData[] datas = Core.PluginManager.GetAllPluginDatas();
            var newDicts = new Dictionary<string, PluginAuthData>();
            foreach (var data in datas)
            {
                if (authDatas.ContainsKey(data.ClassName))
                {
                    newDicts.Add(data.ClassName, authDatas[data.ClassName]);
                }
            }
            authDatas = newDicts;
        }

        /// <summary>
        /// Plugin authorization data
        /// </summary>
        private sealed class PluginAuthData
        {
            string className;
            /// <summary>
            /// Plugin's classname
            /// </summary>
            internal string ClassName
            {
                get { return className; }
            }

            string filePath;
            /// <summary>
            /// Plugin's path
            /// </summary>
            internal string FilePath
            {
                get { return filePath; }
            }

            bool authorized;
            /// <summary>
            /// Authorization state
            /// </summary>
            internal bool Authorized
            {
                get { return authorized; }
                set
                {
                    authorized = value;
                }
            }

            /// <summary>
            /// Get Internal state.
            /// </summary>
            internal string ReflectString
            {
                get { return filePath + '?' + className + '>' + authorized.ToString(); }
                private set
                {
                    if (!(value.Contains("?") && value.Contains(">")))
                        throw new KrileManagementException("Invalid formats");
                    filePath = value.Split('?')[0];
                    className = value.Split('?')[1].Split('>')[0];
                    authorized = Boolean.Parse(value.Split('?')[1].Split('>')[1]);
                }
            }

            /// <summary>
            /// Constructor
            /// </summary>
            internal PluginAuthData()
            {
                className = null;
                filePath = null;
                authorized = false;
            }

            /// <summary>
            /// Constructor with PluginData
            /// </summary>
            /// <param name="pd">Target plugin's data</param>
            internal PluginAuthData(PluginData pd)
            {
                className = pd.ClassName;
                filePath = pd.FilePath;
                authorized = false;
            }

            /// <summary>
            /// Constructor with reflect string
            /// </summary>
            /// <param name="reflect">reflect string</param>
            internal PluginAuthData(string reflect)
                : this()
            {
                ReflectString = reflect;
            }
        }

    }
}
